<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>HashMap v7</title>
    <style>
        div {
            margin-top: 10px;
        }

        html {
            font-size: 12px;
        }

        input[type=text] {
            width: 50px;
        }

        input.long {
            width: 200px;
        }

        ::-webkit-input-placeholder {
            color: white;
        }

        table {
            border-collapse: collapse;
            border-spacing: 0;
        }

        th {
            border-top: 2px solid black;
        }

        td {
            border-bottom: 1px solid black;
        }
    </style>
</head>

<body>
    <div>
        <label for="capacity">capacity </label><input type="text" id="capacity" value="16" placeholder="请输入容量">
        <label for="factor">factor </label><input type="text" id="factor" value="0.75" placeholder="负载因子">
        <input type="button" value="init" id="initBtn">
        <input type="button" value="clear" id="clearBtn">
        <select id="mainFrames"></select>
    </div>
    <div>
        <table id="tbl">
            <tr>
                <th>keys 以,分隔</th>
                <th colspan="5">计算哈希码</th>
                <th>操作</th>
            </tr>
            <tr>
                <td></td>
                <td></td>
                <td>原始hash</td>
                <td>二次hash</td>
                <td>桶下标</td>
                <td>固定hash</td>
                <td></td>
            </tr>
            <tr id="template" style="display:none">
                <td><input type="text" class="key long"></td>
                <td><input type="button" value="index" class="index"></td>
                <td><input type="text" readonly class="codes long"></td>
                <td><input type="text" readonly class="hashes long"></td>
                <td><input type="text" readonly class="indexes long"></td>
                <td><input type="text" value="-1" class="fix"></td>
                <td>
                    <input type="button" value="put" class="put">
                    <input type="button" value="remove" class="remove">
                </td>
            </tr>
        </table>
    </div>
    <div id="canvasDiv">
        <canvas id="main" width="1024" height="400"></canvas>
    </div>
    <select id="transferFrames">
        <option disabled selected>扩容过程</option>
    </select>
    <input type="button" value="play" id="play">
    <div id="canvasDiv">
        <canvas id="from" width="1024" height="400"></canvas>
    </div>
    <div id="canvasDiv">
        <canvas id="to" width="1024" height="400"></canvas>
    </div>
    <script>
        const b_colors = ["#D24D57", "#EB7347", "#FC9D99", "#26A65B", "#AEDD81", "#84AF9B", "#00CCFF", "#D0D0D0", "#2C3E50"]
        const f_colors = ["white", "white", "black", "white", "black", "white", "black", "black", "white"]
        let idx = 1
        function addRow() {
            let row = document.getElementById("template").cloneNode(true)
            let parent = document.getElementById("template").parentNode
            row.removeAttribute("id")
            let random = Math.floor(Math.random() * 9)
            row.style.display = "table-row"
            row.style.backgroundColor = b_colors[random]
            row.style.color = f_colors[random]
            parent.appendChild(row)
        }
        addRow()
        document.getElementById("initBtn").onclick = function () {
            let capacity = document.getElementById("capacity").value;
            let factor = document.getElementById("factor").value;
            let url = `http://localhost:8080/v7/map/init?capacity=${capacity}&factor=${factor}`;
            fetch(url).then(r => r.json()).then(json => {
                loadData();
            });
        }
        document.getElementById("clearBtn").onclick = function () {
            let url = `http://localhost:8080/v7/map/clear`;
            fetch(url).then(r => r.json()).then(json => {
                loadData();
            });
        }
        document.getElementById("tbl").addEventListener("click", function (e) {
            e.stopPropagation();
            let type = e.target.className;
            let tr = e.target.parentNode.parentNode;
            if (type === 'remove') {
                let key = tr.querySelector('.key').value;
                let fix = tr.querySelector('.fix').value;
                let url = `http://localhost:8080/v7/map/remove?key=${key}&fix=${fix}`;
                fetch(url).then(r => r.json()).then(json => {
                    loadData();
                });
            } else if (type === 'put') {
                let key = tr.querySelector('.key').value;
                let fix = tr.querySelector('.fix').value;
                let url = `http://localhost:8080/v7/map/put?key=${key}&fix=${fix}`;
                fetch(url).then(r => r.json()).then(json => {
                    loadData();
                });
            } else if (type === 'index') {
                let key = tr.querySelector(".key").value;
                let fix = tr.querySelector(".fix").value;
                let capacity = document.getElementById("capacity").value;
                let url = `http://localhost:8080/v7/map/index?key=${key}&fix=${fix}&capacity=${capacity}`;
                fetch(url).then(r => r.json()).then(json => {
                    tr.querySelector(".codes").value = json.codes.join(",");
                    tr.querySelector(".hashes").value = json.hashes.join(",");
                    tr.querySelector(".indexes").value = json.indexes.join(",");
                });
            }
        })

        class Rect {
            constructor(x, y, w, h, c, v, vx, vy) {
                this.x = x;     // 大矩形 x
                this.y = y;     // 大矩形 y
                this.w = w;     // 大矩形 w
                this.h = h;     // 大矩形 h
                this.c = c;     // 大矩形 c
                this.v = v;     // 大矩形 v 即值
                this.vx = vx;   // 值 x
                this.vy = vy;   // 值 y
            }
        }
        class Circle {
            constructor(x, y, r, c, v, vx, vy) {
                this.x = x;     // 圆 x
                this.y = y;     // 圆 y
                this.r = r;     // 直径
                this.c = c;     // 圆 c
                this.v = v;     // 圆 v 即值
                this.vx = vx;   // 值 x
                this.vy = vy;   // 值 y
            }
        }
        class Arrow {
            constructor(fx, fy, tx, ty, c) {
                this.fx = fx;
                this.fy = fy;
                this.tx = tx;
                this.ty = ty;
                this.c = c;
            }
        }
        function addToSelect(id, idx, now) {
            let option = document.createElement("option");
            option.value = idx;
            option.innerHTML = idx;
            let select = document.getElementById(id);
            select.appendChild(option);
            if (now === true) {
                select.value = idx;
                select.onchange();
            }
        }
        function clearSelect(id) {
            document.getElementById(id).innerHTML = "";
        }
        document.getElementById("mainFrames").onchange = function () {
            main.drawFrame(this.value - 0)
        }
        document.getElementById("transferFrames").onchange = function () {
            from.drawFrame(this.value - 0)
            to.drawFrame(this.value - 0)
        }
        document.getElementById("play").onclick = function () {
            from.playFrames();
            to.playFrames();
        }
        class Draw {
            constructor(selector) {
                this.canvas = document.querySelector(selector);
                this.ctx = document.querySelector(selector).getContext('2d');
                this.frames = [];
                this.current = 0;
                this.RECT_WIDTH = 20;
                this.RECT_HEIGHT = 16;
                this.SPACEING = 3;
                this.CANVAS_WIDTH = 1024;
                this.CANVAS_HEIGHT = 300;
                this.ARROW_LENGTH = 10;
            }
            reset() {
                this.canvas.width = this.CANVAS_WIDTH;
                this.canvas.height = this.CANVAS_HEIGHT;
            }
            drawArrow(a) {
                let { fx, fy, tx, ty, c } = { ...a };
                let theta = 30;
                let headlen = 10;
                let angle = Math.atan2(fy - ty, fx - tx) * 180 / Math.PI;
                let a1 = (angle + theta) * Math.PI / 180;
                let a2 = (angle - theta) * Math.PI / 180;
                let topX = headlen * Math.cos(a1);
                let topY = headlen * Math.sin(a1);
                let bottomX = headlen * Math.cos(a2);
                let bottomY = headlen * Math.sin(a2);

                this.ctx.save();
                this.ctx.lineWidth = .5;
                this.ctx.beginPath();
                let ax = fx - topX;
                let ay = fy - topY;

                this.ctx.moveTo(ax, ay);
                this.ctx.moveTo(fx, fy);
                this.ctx.lineTo(tx, ty);

                ax = tx + topX;
                ay = ty + topY;
                this.ctx.moveTo(ax, ay);
                this.ctx.lineTo(tx, ty);

                ax = tx + bottomX;
                ay = ty + bottomY;
                this.ctx.lineTo(ax, ay);
                this.ctx.strokeStyle = c || 'black';
                this.ctx.stroke();

                this.ctx.restore();
            }
            drawFrame(idx) {
                this.reset();
                let frame = this.frames[idx];
                frame.rects.forEach(x => {
                    this.drawRect(x)
                })
                frame.arrows.forEach(x => {
                    this.drawArrow(x)
                })
                frame.circles.forEach(x => {
                    this.drawCircle(x)
                })
                this.current = idx;
            }
            drawNextFrame() {
                if (this.current + 1 < this.frames.length) {
                    this.drawFrame(++this.current);
                }
            }
            drawPrevFrame() {
                if (this.current - 1 >= 0) {
                    this.drawFrame(--this.current);
                }
            }
            drawRect(r) {
                this.ctx.save();
                this.ctx.fillStyle = r.c;
                this.ctx.beginPath();
                this.ctx.rect(r.x, r.y, r.w, r.h);
                this.ctx.fill();
                this.ctx.lineWidth = .25;
                this.ctx.stroke();

                this.ctx.fillStyle = '#ffffff';
                this.ctx.font = '1em Arial';
                this.ctx.textAlign = 'center';
                this.ctx.fillText(r.v, r.vx, r.vy);
                this.ctx.restore();

            }
            drawCircle(r) {
                this.ctx.save();

                this.ctx.fillStyle = r.c;
                this.ctx.beginPath();
                this.ctx.arc(r.x, r.y, r.r, 0, (Math.PI / 180) * 360, false);
                this.ctx.fill();
                this.ctx.lineWidth = .25;
                this.ctx.stroke();

                this.ctx.fillStyle = '#ffffff';
                this.ctx.font = '1em Arial';
                this.ctx.textAlign = 'center';
                this.ctx.fillText(r.v, r.vx, r.vy);

                this.ctx.restore();
            }
            addFrame(obj) {
                let { tables, details } = { ...obj };
                const frame = {};
                frame.rects = [];
                frame.circles = [];
                frame.arrows = [];
                if (tables) {
                    let x = 0;
                    //console.log(details);
                    for (let i = 0; i < tables.length; i++) {
                        let w = tables[i].size === 0 ? this.RECT_WIDTH : (tables[i].size * (this.RECT_WIDTH + this.SPACEING) - this.SPACEING);
                        if (tables[i] !== "empty") {
                            let h = this.RECT_HEIGHT;
                            let y = 0;
                            let c = '#26A65B';
                            frame.rects.push(new Rect(
                                x, y, w, h, c,
                                tables[i].v, x + w / 2, y + h / 2 + 5
                            ));
                        }
                        let detail = details[i];
                        if (detail !== "empty") {
                            if (detail.type === 'list') {
                                let c = '#2C3E50';
                                let r = this.RECT_WIDTH / 2;
                                let y = this.RECT_HEIGHT + this.SPACEING;
                                let data = detail.data;
                                for (let j = 0; j < data.length; j++) {
                                    frame.circles.push(new Circle(
                                        x + r, y + r, r, c,
                                        data[j], x + r, y + r + 5
                                    ));
                                    frame.arrows.push(new Arrow(x + r, y + this.RECT_WIDTH, x + r, y + this.RECT_WIDTH + this.ARROW_LENGTH, 'blank'));
                                    y += (this.RECT_WIDTH + this.ARROW_LENGTH);
                                }
                            } else if (detail.type === 'tree') {
                                let tx = x;
                                let r = this.RECT_WIDTH / 2;
                                let y = this.RECT_HEIGHT + this.SPACEING;
                                let data = detail.data;
                                let a = [];
                                for (let j = 0; j < data.length; j++) {
                                    let c = data[j].red;
                                    let y = this.RECT_HEIGHT + this.SPACEING + ((this.RECT_WIDTH + this.ARROW_LENGTH) * data[j].y);
                                    frame.circles.push(new Circle(
                                        tx + r, y + r, r, c,
                                        data[j].data, tx + r, y + r + 5
                                    ));
                                    a.push({ x: tx + r, y: y + r });
                                    tx += this.RECT_WIDTH + this.SPACEING;
                                }
                                for (let j = 0; j < data.length; j++) {
                                    let pi = data[j].pi;
                                    if (pi >= 0) {
                                        frame.arrows.push(new Arrow(a[pi].x, a[pi].y, a[j].x, a[j].y, 'blank'));
                                    }
                                }
                            } else if (detail.type === 'forwarding') {
                                let c = '#D24D57';
                                let r = this.RECT_WIDTH / 2;
                                let y = this.RECT_HEIGHT + this.SPACEING;
                                frame.circles.push(new Circle(
                                    x + r, y + r, r, c,
                                    detail.data, x + r, y + r + 5
                                ));
                            }
                        }
                        x += w + this.SPACEING;
                    }
                    this.CANVAS_WIDTH = x;
                } else {
                    this.CANVAS_WIDTH = 1024;
                }
                this.frames.push(frame);
            }
            clearFrames() {
                this.frames = [];
                this.current = 0;
                //clearSelect();
            }
            playFrames() {
                if (this.current >= this.frames.length - 1) {
                    return;
                }
                this.drawFrame(++this.current);
                setTimeout(() => {
                    this.playFrames();
                }, 300);
            }
        }

        const main = new Draw("#main");
        const from = new Draw("#from");
        const to = new Draw("#to");

        function findParentIndex(array, value) {
            for (let j = 0; j < array.length; j++) {
                if (value.parent === array[j].data) {
                    return j;
                }
            }
            return -1;
        }

        function addFrame(draw, buckets) {
            //draw.clearFrames();
            let tables = [];
            let details = [];
            for (let i = 0; i < buckets.length; i++) {
                let t = buckets[i];
                let tt = { v: i, size: 0 };

                if (t.type) {
                    if (t.type === "list" && t.value.length > 0) {
                        details.push({ v: i, data: t.value.map(e => e), type: 'list' });
                    } else if (t.type === "tree") {
                        let array = t.value;
                        for (let j = 0; j < array.length; j++) {
                            array[j].pi = findParentIndex(array, array[j]);
                        }
                        details.push({ v: i, data: array, type: 'tree' });
                        tt.size = t.value.length;
                    } else if (t.type === "forwarding") {
                        details.push({ v: i, data: t.value, type: 'forwarding' });
                    }
                } else {
                    details.push("empty");
                }
                tables.push(tt);
            }
            // console.log(details)
            draw.addFrame({ tables: tables, details: details });
        }
        function loadData() {
            fetch("http://localhost:8080/v7/map/tree").then(r => r.json()).then(data => {
                addFrame(main, data.buckets);
                addToSelect("mainFrames", main.frames.length - 1, true);
                if (data.records) {
                    for (let i = 0; i < data.records.length; i++) {
                        let r = data.records[i];
                        if (r.type === "from") {
                            addFrame(from, r.list);
                            addToSelect("transferFrames", from.frames.length - 1, false);
                        } else if (r.type === "to") {
                            addFrame(to, r.list);
                        }
                    }
                }
            });
        }
        loadData();
    </script>
</body>

</html>